﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using BloxGin.Graphics;
using BloxGin.Blocks;
using Microsoft.Xna.Framework.Graphics.PackedVector;

namespace BloxGame
{
    public enum FaceDirection { Left, Right, Top, Bottom, Front, Back };
    /// <summary>
    /// A chunk that should be rendered
    /// </summary>
    public class Chunk
    {
         public const byte SIZE = 16;
        const byte HEIGHT = World.HEIGHT;
        public int X { get; set; }
        public int Z { get; set; }
        public BoundingBox BoundingBox { get; set; }

        DynamicVertexBuffer vertexBuffer;
        List<VertexBlock> shapeVerticesList;
        VertexBlock[] shapeVertices;

        int faces = 0;

        World world;

        public Chunk(GraphicsDevice device, World world, int startX, int startY)
        {
            
            this.world = world;
            X = startX;
            Z = startY;
            BoundingBox = new BoundingBox(new Vector3(X, 0, Z), new Vector3(X, 0, Z) + new Vector3(SIZE, HEIGHT, SIZE));

            Rebuild(device, world);

        }

        public void Rebuild(GraphicsDevice device, World world)
        {
            this.world = world;

            shapeVerticesList = new List<VertexBlock>(36 * SIZE * HEIGHT);

                
            for (byte x = 0; x < SIZE; x++)
                for (byte y = 0; y < HEIGHT; y++)
                    for (byte z = 0; z < SIZE; z++)
                    {
                        int xi = x + X;
                        int yi = y;
                        int zi = z + Z;
                        Block b = world.GetBlock(xi, yi, zi);
                        if (b == BlockTypes.None)
                            continue;
                        if (xi == 1 || xi >= World.SIZE-1)
                            continue;
                        if (yi <= 1)
                            continue;
                        if (zi == 1 || zi >= World.SIZE-1)
                            continue;

                        bool top = world.GetBlock(xi, yi + 1, zi) != 0;
                        bool bottom = world.GetBlock(xi, yi - 1, zi) != 0;
                        bool left = world.GetBlock(xi - 1, yi, zi) != 0;
                        bool right = world.GetBlock(xi + 1, yi, zi) != 0;
                        bool front = world.GetBlock(xi, yi, zi - 1) != 0;
                        bool back = world.GetBlock(xi, yi, zi + 1) != 0;


                        bool notDraw = top && bottom && left && right && front && back;
                        if (notDraw)
                            continue;

                        if (!top)
                            AddFace((byte)x, y, z, FaceDirection.Top);
                        if (!bottom)
                            AddFace((byte)x, y, z, FaceDirection.Bottom);
                        if (!left)
                            AddFace((byte)x, y, z, FaceDirection.Left);
                        if (!right)
                            AddFace((byte)x, y, z, FaceDirection.Right);
                        if (!front)
                            AddFace((byte)x, y, z, FaceDirection.Front);
                        if (!back)
                            AddFace((byte)x, y, z, FaceDirection.Back);
                    }
                

            shapeVertices = shapeVerticesList.ToArray();
            if (shapeVertices.Length == 0)
            {
                shapeVertices = null;
                return;

            }

            vertexBuffer = new DynamicVertexBuffer(device, typeof(VertexBlock), shapeVerticesList.Count, BufferUsage.WriteOnly);
            vertexBuffer.SetData<VertexBlock>(shapeVertices);
           
        }

        public override string ToString()
        {
            return (shapeVertices.Length / (X * 7 + Z * 11)).ToString();
        }

        public void AddSolid(byte x, byte y, byte z,GraphicsDevice device)
        {
            int xi = x + X;
            int yi = y;
            int zi = z + Z;
            Block b = world.GetBlock(xi, yi, zi);
            if (b == BlockTypes.None)
                return;

            bool top = world.GetBlock(xi, yi + 1, zi) != 0;
            bool bottom = world.GetBlock(xi, yi - 1, zi) != 0;
            bool left = world.GetBlock(xi - 1, yi, zi) != 0;
            bool right = world.GetBlock(xi + 1, yi, zi) != 0;
            bool front = world.GetBlock(xi, yi, zi - 1) != 0;
            bool back = world.GetBlock(xi, yi, zi + 1) != 0;


            bool notDraw = top && bottom && left && right && front && back;
            if (notDraw)
                return;

            if (!top)
                AddFace(x, y, z, FaceDirection.Top);
            if (!bottom)
                AddFace(x, y, z, FaceDirection.Bottom);
            if (!left)
                AddFace(x, y, z, FaceDirection.Left);
            if (!right)
                AddFace(x, y, z, FaceDirection.Right);
            if (!front)
                AddFace(x, y, z, FaceDirection.Front);
            if (!back)
                AddFace(x, y, z, FaceDirection.Back);

            shapeVertices = shapeVerticesList.ToArray();

            vertexBuffer = new DynamicVertexBuffer(device, typeof(VertexBlock), shapeVertices.Length, BufferUsage.None);

            vertexBuffer.SetData<VertexBlock>(shapeVertices, 0, shapeVertices.Length, SetDataOptions.Discard);

        }

        public void RemoveSolid(byte x, byte y, byte z, GraphicsDevice device)
        {
            /*
            int xi = x + X;
            int yi = y;
            int zi = z + Z;
            Block b = world.GetBlock(xi, yi, zi);
            if (b == BlockTypes.None)

                return;

            bool top = world.GetBlock(xi, yi + 1, zi) != 0;
            bool bottom = world.GetBlock(xi, yi - 1, zi) != 0;
            bool left = world.GetBlock(xi - 1, yi, zi) != 0;
            bool right = world.GetBlock(xi + 1, yi, zi) != 0;
            bool front = world.GetBlock(xi, yi, zi - 1) != 0;
            bool back = world.GetBlock(xi, yi, zi + 1) != 0;


            bool notDraw = top && bottom && left && right && front && back;
            if (notDraw)
                return;

            if (!top)
                AddFace(x, y, z, FaceDirection.Top);
            if (!bottom)
                AddFace(x, y, z, FaceDirection.Bottom);
            if (!left)
                AddFace(x, y, z, FaceDirection.Left);
            if (!right)
                AddFace(x, y, z, FaceDirection.Right);
            if (!front)
                AddFace(x, y, z, FaceDirection.Front);
            if (!back)
                AddFace(x, y, z, FaceDirection.Back);

            shapeVertices = shapeVerticesList.ToArray();

            vertexBuffer = new DynamicVertexBuffer(device, typeof(VertexBlock), shapeVertices.Length, BufferUsage.None);

            vertexBuffer.SetData<VertexBlock>(shapeVertices, 0, shapeVertices.Length, SetDataOptions.Discard);
            // vertexBuffer.SetData<VertexBlock>(shapeVertices);
            */
            Rebuild(device, world);



        }

        public void AddFace(byte x, byte y, byte z, FaceDirection direction)
        {
            int xi = x + X;
            int yi = y;
            int zi = z + Z;
            byte blockType = world.GetBlock(xi, yi, zi);

            HalfVector2[] UVS = GetUVS((byte)GetTextureType((BlockTypes)blockType));

            HalfVector2[] topUVS = UVS;

            if (blockType == (byte)BlockTypes.Dirt && world.GetBlock(xi, yi + 1, zi) == 0)
            {
                topUVS = GetUVS((byte)TextureTypes.Grass);
                UVS = GetUVS((byte)TextureTypes.SideGrass);
            }
            HalfVector2 textureTopLeft = UVS[0];
            HalfVector2 textureTopRight = UVS[1];
            HalfVector2 textureBottomLeft = UVS[2];
            HalfVector2 textureBottomRight = UVS[3];

            ByteVector4 shapePosition = new ByteVector4((byte)(x), (byte)(y), (byte)(z));

            ByteVector4 topLeftFront = new ByteVector4(0, 1, 0) + shapePosition;

            ByteVector4 bottomLeftFront = shapePosition +
               new ByteVector4(0, 0, 0);
            ByteVector4 topRightFront = shapePosition +
                new ByteVector4(1, 1, 0);
            ByteVector4 bottomRightFront = shapePosition +
                new ByteVector4(1, 0, 0);
            ByteVector4 topLeftBack = shapePosition +
                new ByteVector4(0, 1, 1);
            ByteVector4 topRightBack = shapePosition +
                new ByteVector4(1, 1, 1);
            ByteVector4 bottomLeftBack = shapePosition +
                new ByteVector4(0, 0, 1);
            ByteVector4 bottomRightBack = shapePosition +
                new ByteVector4(1, 0, 1);


            Vector3 frontNormal = new Vector3(0.0f, 0.0f, 1.0f);
            Vector3 backNormal = new Vector3(0.0f, 0.0f, -1.0f);
            Vector3 topNormal = new Vector3(0.0f, 1.0f, 0.0f);
            Vector3 bottomNormal = new Vector3(0.0f, -1.0f, 0.0f);
            Vector3 leftNormal = new Vector3(-1.0f, 0.0f, 0.0f);
            Vector3 rightNormal = new Vector3(1.0f, 0.0f, 0.0f);


           
            switch (direction)
            {
                case FaceDirection.Left:

                    shapeVerticesList.Add(new VertexBlock(
                   topLeftFront, leftNormal, textureTopRight));
                    shapeVerticesList.Add(new VertexBlock(
                                    bottomLeftBack, leftNormal, textureBottomLeft));
                    shapeVerticesList.Add(new VertexBlock(
                                   bottomLeftFront, leftNormal, textureBottomRight));
                    shapeVerticesList.Add(new VertexBlock(
                             topLeftBack, leftNormal, textureTopLeft));
                    shapeVerticesList.Add(new VertexBlock(
                         bottomLeftBack, leftNormal, textureBottomLeft));
                    shapeVerticesList.Add(new VertexBlock(
                                topLeftFront, leftNormal, textureTopRight));

                    break;
                case FaceDirection.Right:
                    shapeVerticesList.Add(new VertexBlock(
                   topRightFront, rightNormal, textureTopLeft));
                    shapeVerticesList.Add(new VertexBlock(
                         bottomRightFront, rightNormal, textureBottomLeft));
                    shapeVerticesList.Add(new VertexBlock(
                        bottomRightBack, rightNormal, textureBottomRight));
                    shapeVerticesList.Add(new VertexBlock(
                        topRightBack, rightNormal, textureTopRight));
                    shapeVerticesList.Add(new VertexBlock(
                            topRightFront, rightNormal, textureTopLeft));
                    shapeVerticesList.Add(new VertexBlock(
                              bottomRightBack, rightNormal, textureBottomRight));

                    break;


                case FaceDirection.Top:
                    shapeVerticesList.Add(new VertexBlock(
                 topLeftFront, topNormal, topUVS[2]));
                    shapeVerticesList.Add(new VertexBlock(
                         topRightBack, topNormal, topUVS[1]));
                    shapeVerticesList.Add(new VertexBlock(
                          topLeftBack, topNormal, topUVS[0]));
                    shapeVerticesList.Add(new VertexBlock(
                         topLeftFront, topNormal, topUVS[2]));
                    shapeVerticesList.Add(new VertexBlock(
                           topRightFront, topNormal, topUVS[3]));
                    shapeVerticesList.Add(new VertexBlock(
                         topRightBack, topNormal, topUVS[1]));
                    break;
                case FaceDirection.Bottom:
                    shapeVerticesList.Add(new VertexBlock(
                  bottomLeftFront, bottomNormal, textureTopLeft));
                    shapeVerticesList.Add(new VertexBlock(
                              bottomLeftBack, bottomNormal, textureBottomLeft));
                    shapeVerticesList.Add(new VertexBlock(
                                 bottomRightBack, bottomNormal, textureBottomRight));
                    shapeVerticesList.Add(new VertexBlock(
                               bottomLeftFront, bottomNormal, textureTopLeft));
                    shapeVerticesList.Add(new VertexBlock(
                                   bottomRightBack, bottomNormal, textureBottomRight));
                    shapeVerticesList.Add(new VertexBlock(
                          bottomRightFront, bottomNormal, textureTopRight));
                    break;
                case FaceDirection.Front:
                    shapeVerticesList.Add(new VertexBlock(
                topLeftFront, frontNormal, textureTopLeft));
                    shapeVerticesList.Add(new VertexBlock(
                        bottomLeftFront, frontNormal, textureBottomLeft));
                    shapeVerticesList.Add(new VertexBlock(
                        topRightFront, frontNormal, textureTopRight));
                    shapeVerticesList.Add(new VertexBlock(
                         bottomLeftFront, frontNormal, textureBottomLeft));
                    shapeVerticesList.Add(new VertexBlock(
                                    bottomRightFront, frontNormal, textureBottomRight));
                    shapeVerticesList.Add(new VertexBlock(
                             topRightFront, frontNormal, textureTopRight));
                    break;
                case FaceDirection.Back:
                    shapeVerticesList.Add(new VertexBlock(
                 topLeftBack, backNormal, textureTopRight));
                    shapeVerticesList.Add(new VertexBlock(
                         topRightBack, backNormal, textureTopLeft));
                    shapeVerticesList.Add(new VertexBlock(
                          bottomLeftBack, backNormal, textureBottomRight));
                    shapeVerticesList.Add(new VertexBlock(
                        bottomLeftBack, backNormal, textureBottomRight));
                    shapeVerticesList.Add(new VertexBlock(
                         topRightBack, backNormal, textureTopLeft));
                    shapeVerticesList.Add(new VertexBlock(
                        bottomRightBack, backNormal, textureBottomLeft));
                    break;
                default:
                    return;
            }

            faces++;
        }

        private TextureTypes GetTextureType(BlockTypes type)
        {
            switch (type)
            {
                case BlockTypes.None:
                    return TextureTypes.None;
                case BlockTypes.Rock:
                    return TextureTypes.Rock;
                case BlockTypes.Dirt:
                    return TextureTypes.Dirt;
                default:
                    return TextureTypes.None;
            }
        }

        private HalfVector2[] GetUVS(byte textureIndex)
        {

   
            int yi = textureIndex / 16;
            int xi = textureIndex % 16;

            float ofs = 1f / 16;

            float yOfs = yi * ofs;
            float xOfs = xi * ofs;

           
            HalfVector2 textureTopLeft = new HalfVector2(xOfs + ofs, yOfs);
            HalfVector2 textureTopRight = new HalfVector2(xOfs, yOfs);
            HalfVector2 textureBottomLeft = new HalfVector2(xOfs + ofs, yOfs + ofs);
            HalfVector2 textureBottomRight = new HalfVector2(xOfs, yOfs + ofs);

           

            return new HalfVector2[4] { textureTopLeft, textureTopRight, textureBottomLeft, textureBottomRight };
        }

        public void Draw(GameTime gameTime, GraphicsDevice device, Effect effect)
        {
            if (shapeVertices == null)
                return;
            
            device.SetVertexBuffer(vertexBuffer);

            foreach (EffectPass pass in effect.CurrentTechnique.Passes)
            {
                pass.Apply();

                device.DrawPrimitives(PrimitiveType.TriangleList, 0, faces * 2);

            }
        }


    }
}
